package com.fjec.common.ys;

import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.*;

/**
 * 签名验签工具
 *
 * @author chang
 */
@Slf4j
public class SignUtil {

    public static Map<String, Object> certMap = new HashMap<>();

    /**
     * 除去数组中的空值和签名参数
     *
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {

        Map<String, String> result = new HashMap<>();

        if (sArray == null || sArray.size() <= 0) {
            return result;
        }

        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || Strings.isNullOrEmpty(value) || key.equalsIgnoreCase("sign")) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    /**
     * 遍历以及根据重新排序
     *
     * @param sortedParams
     * @return
     */
    public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key);
            if (!Strings.isNullOrEmpty(key)
                    && !Strings.isNullOrEmpty(value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }
        }
        return content.toString();
    }

    /**
     * 签名
     *
     * @param content
     * @param charset
     * @param pfxCertFileInputStream
     * @param certPwd
     * @return
     * @throws Exception
     */
    public static String rsaSign(String content, String charset, InputStream pfxCertFileInputStream, String certPwd) throws Exception {
//        try {
        if (log.isDebugEnabled()) {
            log.debug("进入签名方法：content[{}], charset[{}]", content, charset);
        }
        PrivateKey priKey = getPrivateKeyFromPKCS12(certPwd, pfxCertFileInputStream);
        java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA");
        signature.initSign(priKey);

        if (Strings.isNullOrEmpty(charset)) {
            signature.update(content.getBytes());
        } else {
            signature.update(content.getBytes(charset));
        }

        byte[] signed = signature.sign();
        String sign = new String(Base64.encodeBase64(signed), charset);

        if (log.isDebugEnabled()) {
            log.debug("签名完毕：content[{}], charset[{}], sign[{}]", content, charset, sign);
        }
        return sign;
//        } catch (Exception e) {
//            throw new Exception("RSAcontent = " + content + "; charset = " + charset, e);
//        }
    }

    /**
     * 把参数签名
     *
     * @param params
     * @param charset
     * @param pfxCertFile
     * @return
     * @throws Exception
     */
    public static String rsaSign(Map<String, String> params, String charset, String pfxCertFile, String certPwd) {
        String signContent = getSignContent(params);
        String sign = "";
        InputStream is = null;
        try {
            is = new FileInputStream(pfxCertFile);
            sign = rsaSign(signContent, charset, is, certPwd);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sign;
    }

    public static boolean rsaCheckContent(InputStream publicCertFileInputStream, Map<String, String> params,
                                          String sign, String charset) throws Exception {
        String content = SignUtil.createLinkString(SignUtil.paraFilter(params));

        return rsaCheckContent(publicCertFileInputStream, content, sign, charset);
    }

    /**
     * 验签
     *
     * @param publicCert
     * @param content
     * @param sign
     * @param charset
     * @return
     * @throws Exception
     */
    public static boolean rsaCheckContent(InputStream publicCert, String content,
                                          String sign, String charset) throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("进入验签: content[{}], sign[{}], charset[{}]", content, sign, charset);
        }
        java.security.Signature signetCheck = java.security.Signature.getInstance("SHA1WithRSA");
        signetCheck.initVerify(getPublicKeyFromCert(publicCert));
        signetCheck.update(content.getBytes(charset));
        return signetCheck.verify(Base64.decodeBase64(sign.getBytes(charset)));
    }

    /**
     * 读取公钥，x509格式
     *
     * @param ins
     * @return
     * @throws Exception
     * @see
     */
    public static PublicKey getPublicKeyFromCert(InputStream ins) throws Exception {
        Map<String, Object> certMap = new java.util.concurrent.ConcurrentHashMap<>();

        PublicKey pubKey = (PublicKey) certMap.get("PublicKey");
        if (pubKey != null) {
            return pubKey;
        }

        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Certificate cac = cf.generateCertificate(ins);
            pubKey = cac.getPublicKey();
            certMap.put("PublicKey", pubKey);
        } catch (Exception e) {
            if (ins != null)
                ins.close();
            throw e;
        } finally {
            if (ins != null) {
                ins.close();
            }
        }

        return pubKey;
    }

    /**
     * 读取PKCS12格式的key（私钥）pfx格式
     *
     * @param password
     * @param ins
     * @return
     * @throws Exception
     * @see
     */
    public static PrivateKey getPrivateKeyFromPKCS12(String password, InputStream ins) throws Exception {
        Map<String, Object> certMap = new java.util.concurrent.ConcurrentHashMap<>();
        PrivateKey priKey = (PrivateKey) certMap.get("PrivateKey");
        if (priKey != null) {
            return priKey;
        }

        KeyStore keystoreCA = KeyStore.getInstance("PKCS12");
        try {
            // 读取CA根证书
            keystoreCA.load(ins, password.toCharArray());

            Enumeration<?> aliases = keystoreCA.aliases();
            String keyAlias = null;
            if (aliases != null) {
                while (aliases.hasMoreElements()) {
                    keyAlias = (String) aliases.nextElement();
                    // 获取CA私钥
                    priKey = (PrivateKey) (keystoreCA.getKey(keyAlias, password.toCharArray()));
                    if (priKey != null) {
                        certMap.put("PrivateKey", priKey);
                        break;
                    }
                }
            }
        } catch (Exception e) {
            if (ins != null)
                ins.close();
            throw e;
        } finally {
            if (ins != null) {
                ins.close();
            }
        }

        return priKey;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {

        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);

            if (i == keys.size() - 1) {
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }

        return prestr;
    }
}
